import numpy as np
from keras.preprocessing import sequence   #该函数是将序列转化为经过填充以后的一个长度相同的新序列新序列
# 根据样本集最大词汇数选择最大特征数，应大于样本集最大词汇数
max_features = 25000

# n-gram特征的范围，一般选择为2

####################################################################
              #学习了特征处理过程的四步曲:
              #第一步: 进行词汇映射
              #第二步: 将向量进行合适截断
              #第三步: 加入n-gram特征
              #第四步: 将向量进行最长补齐
####################################################################
ngram_range = 2

def create_ngram_set(input_list, ngram_value=2):
    """
    从列表中提取n-gram特征
    {(4, 9), (4, 1), (1, 4), (9, 4)}
    """
    return set(zip(*[input_list[i:] for i in range(ngram_value)])) # 进行索引的组合形成新的特征



def get_ti_and_nmf(x_train, ti_path, ngram_range):
    """从训练数据中获得token_indice和新的max_features"""
    # >>> token_indice = {(1, 3): 1337, (9, 2): 42, (4, 5): 2017}
    # 创建一个盛装n-gram特征的集合.
    ngram_set = set()
    # 遍历每一个数值映射后的列表
    for input_list in x_train:
        # 遍历可能存在2-gram, 3-gram等
        for i in range(2, ngram_range + 1):
            # 获得对应的n-gram表示
            set_of_ngram = create_ngram_set(input_list, ngram_value=i)
            # 更新n-gram集合
            ngram_set.update(set_of_ngram)  #
            # update() 方法用于修改当前集合，可以添加新的元素或集合到当前集合中,则该元素只会出现一次。

    # 去除掉(0, 0)这个2-gram特征
    ngram_set.discard(tuple([0]*ngram_range))
    # 将n-gram特征映射成整数.
    # 为了避免和之前的词汇特征冲突，n-gram产生的特征将从max_features+1开始
    start_index = max_features + 1
    # 得到对n-gram表示与对应特征值的字典
    token_indice = {v: k + start_index for k, v in enumerate(ngram_set)}
    # 将token_indice写入文件以便预测时使用
    with open(ti_path, "w") as f:
        f.write(str(token_indice))
    # token_indice的反转字典，为了求解新的最大特征数
    indice_token = {token_indice[k]: k for k in token_indice}
    # 获得加入n-gram之后的最大特征数
    new_max_features = np.max(list(indice_token.keys())) + 1
    return token_indice, new_max_features


def add_ngram(sequences, token_indice, ngram_range=2):
    """
    将n-gram特征加入到训练数据中
    如: adding bi-gram
    [[1, 3, 4, 5, 1337, 2017], [1, 3, 7, 9, 2, 1337, 42]]
    """
    new_sequences = []
    # 遍历序列列表中的每一个元素作为input_list, 即代表一个句子的列表
    for input_list in sequences:
        # copy一个new_list
        new_list = input_list[:].tolist()
        # 遍历n-gram的value，至少从2开始
        for ngram_value in range(2, ngram_range + 1):
            # 遍历各个可能的n-gram长度
            for i in range(len(new_list) - ngram_value + 1):
                # 获得input_list中的n-gram表示
                ngram = tuple(new_list[i:i + ngram_value])
                # 如果在token_indice中，则追加相应的数值特征
                if ngram in token_indice:
                    new_list.append(token_indice[ngram])
        new_sequences.append(new_list)
    return np.array(new_sequences)


# cutlen根据数据分析中句子长度分布，覆盖90%语料的最短长度.
cutlen = 60
def padding(x_train, cutlen):
    return sequence.pad_sequences(x_train, cutlen)

def align(x_train):
    """用于向量按照最长长度进行补齐"""
    # 获得所有句子长度的最大值
    maxlen = max(list(map(lambda x: len(x), x_train)))
    # 调用padding函数
    x_train = padding(x_train, maxlen)
    return x_train, maxlen